﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Bouyei.Geo.GeoParsers
{
    using Geometries;
    using Converters;

    public class PostGisParser : BaseBytesParser
    {
        public GeoType geometryType { get; set; }
        public ByteOrder byteOrder { get;private set; }

        public int SRID { get;private set; }

        private BitExtensions bitConvert = null;


        public PostGisParser(byte[] array)
            : base(array)
        {
            this.array = array;

            byteOrder = (ByteOrder)array[0];

            bitConvert = new BitExtensions(byteOrder);
        }

        public PostGisParser()
            : base(null)
        {

        }

        public unsafe Geometry FromReader()
        {
            fixed (byte* ptr = &array[1])
            {
                geometryType = (GeoType)bitConvert.ToInt32(ptr);
                byte* start = ptr + 4;

                switch (geometryType)
                {
                    case GeoType.POINT:
                        {
                            var point = new WkbPoint(bitConvert, start);
                            return new Geometry(point.Coord);
                        }
                    case GeoType.MULTIPOINT:
                        {
                            var multipoint = new WkbMultiPoint(bitConvert, start);
                            return new Geometry(GeoType.MULTIPOINT, multipoint.GetCoordinates());
                        }
                    case GeoType.LINESTRING:
                        {
                            var linestring = new WkbLineString(bitConvert, start);
                            return new Geometry(GeoType.LINESTRING, linestring.GetCoordinates());
                        }
                    case GeoType.MULTILINESTRING:
                        {
                            var multilinestring = new WkbMultiLineString(bitConvert, start);
                            return new Geometry(GeoType.MULTILINESTRING, multilinestring.GetCoordinates());
                        }
                    case GeoType.POLYGON:
                        {
                            var polygon = new WkbPolygon(bitConvert, start);
                            if (polygon.Count == 1) return new Geometry(GeoType.POLYGON, polygon[0].Coords);
                            else if (polygon.Count > 1) return new Geometry(polygon[0].Coords, polygon.GetInteriorRing());
                            break;
                        }
                    case GeoType.MULTIPOLYGON:
                        {
                            var multiPolygon = new WkbMultiPolygon(bitConvert, start);
                            List<GeoPolygon> polygons = new List<GeoPolygon>(multiPolygon.Count);

                            foreach (WkbPolygon polygon in multiPolygon)
                            {
                                if (polygon.Count == 1)
                                    polygons.Add(new GeoPolygon(polygon.GetOuterRing()));
                                else if (polygon.Count >= 2)
                                    polygons.Add(new GeoPolygon(polygon.GetOuterRing(), polygon.GetInteriorRing()));
                            }
                            return new Geometry(polygons.ToArray());
                        }
                    default:
                        throw new Exception("geometryType not supported" + geometryType);
                }
            }
            return null;
        }

        private unsafe void WriteHeader(byte* ptr, int srid, GeoType geoType)
        {
            *ptr = (byte)(byteOrder);

            var typeword = (uint)geoType;
            if (srid != -1)
            {
                typeword |= 0x20000000;
            }
            bitConvert.WriteTo(ptr + 1, typeword);

            if (srid != -1)
            {
                bitConvert.WriteTo(ptr + 4, (uint)geoType);
                ptr += 9;
            }
            else
            {
                ptr += 5;
            }
        }

        private unsafe void WriteFromPolygon(byte* ptr, Geometry geometry)
        {
            var seqs = geometry.GetGemoetry(0);

            int count = seqs.Count;
            bitConvert.WriteTo(ptr, count);
            ptr += 4;
 
            for (int i = 0; i < count; ++i)
            {
                WriteFromSequence(ptr, seqs[i]);
            }
        }

        private unsafe void WriteFromMultiPoint(byte *ptr,int srid,Geometry geometry)
        {
            var seqs = geometry.GetGemoetry(0);

            WriteHeader(ptr, srid, GeoType.MULTIPOINT);
            int count = seqs[0].Count;
            bitConvert.WriteTo(ptr, count);
            ptr += 4;

            for (int i = 0; i < count; ++i)
            {
                WriteHeader(ptr, srid, GeoType.POINT);
                WriteFromSequence(ptr, seqs[i]);
            }
        }

        private unsafe void WriteFromSequence(byte * ptr,GeoSequence sequence)
        {
            int count = sequence.Count;
            if (count >= 2)
            {
                bitConvert.WriteTo(ptr, count);
                ptr += 4;
            }

            for(int i = 0; i < count; ++i)
            {
                bitConvert.WriteTo(ptr, sequence[i].X);
                ptr += 8;
                bitConvert.WriteTo(ptr, sequence[i].Y);
                ptr += 8;
            }
        }

        private unsafe void ReadHeader(byte* ptr)
        {
            byteOrder = (ByteOrder)(*ptr);
          
            int typeword = bitConvert.ToInt32(ptr + 1);
          
            var geoType = (GeoType)(typeword & 0x1FFFFFFF);
            
            if ((typeword & 0x20000000) != 0)
            {
                SRID = bitConvert.ToInt32(ptr + 4);
                ptr += 9;
            }
            else
            {
                SRID = -1;
                ptr += 5;
            }
        }
    }
}
